/*---------------------------------------------------------------------------
 * �2005 NORSYS
 * main author : nono
 *
 * This software is a computer program whose purpose is to provide abstraction
 * for accessing directory data sources within java applications.
 *
 * This software is governed by the CeCILL  license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 *
 * Created on Oct 19, 2005
 * --------------------------------------------------------------------------*/
package speculoos.jndi.mappers;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import speculoos.core.FunctorMapper;
import speculoos.core.Mapper;
import speculoos.core.MapperException;
import speculoos.jndi.SearchMapper;
import speculoos.utils.VariableString;

/**
 * Implementation of search operation on JNDI/LDAP backend.
 * <p>
 * A {@link javax.naming.directory.SearchControls} object is attached to this
 * kind of mapper. It is filled by the parameters of mapping operations:
 * <dl>
 * <dd>countLimit</dd>
 * <dt>Fixed the maximum number of elements that may be returned by the query
 * </dt>
 * <dd>derefLinkFlag</dd>
 * <dt>Are links dereferenced during search ? This is a boolean value</dt>
 * <dd>returningAttributes</dd>
 * <dt>A list of attribute names returned by the query</dt>
 * <dd>returningObjFlag</dd>
 * <dt>A flag denoting whether or not this query returns objects</dt>
 * <dd>searchScope
 * <dd>
 * <dt>The search scope of the query: may be "object", "children", "descendant"
 * </dt>
 * <dd>timeLimit</dd>
 * <dt>Timeout of the query in milliseconds</dt>
 * </dl>
 * </p>
 * <p>
 * If no filter is defined with the method {@link #setFilter(VariableString)},
 * then the input object is used as an implicit filter (if it exists): each property
 *  is converted to an equality test filter using the <em>output map</em>
 * for name conversions and the content of the map's entry as a value.
 * </p>
 * <p>
 * <p>If no explicit or implicit fliter are set, then we use 
 * the root DN as a query and return the object at this address.
 * </p>
 * 
 * @author nono
 * @version $Id: SearchMapperImpl.java 61 2005-10-21 14:54:44Z
 *          /C=FR/ST=Nord/L=Lille/O=Norsys SA/OU=UE/CN=Arnaud
 *          Bailly/emailAddress=abailly@norsys.fr $
 * @see <a href="/doc/conception/spec-spi.html">XML configuration for JNDI</a>
 */
public class SearchMapperImpl extends JNDIMapper implements SearchMapper {

	public static final String DEFAULT_QUERY = "(objectclass=*)";

	/*
	 * this attribute is set if filter is deduced from input object.
	 */
	private FilterFromInput filter = null;

	/*
	 * controls for this search
	 */
	SearchControls controls = new SearchControls();

	/*
	 * transformer for generating outputs from SearchResults.
	 */
	private Mapper output = null;

	/**
	 * Creates a named mapper.
	 * 
	 * @param value
	 *            the name of this mapper.
	 */
	public SearchMapperImpl(String value) {
		super(value);
	}

	/**
	 * Default constructor for creating anonymous mapper.
	 * 
	 */
	public SearchMapperImpl() {
		this("search");
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see fr.norsys.mapper.core.jndi.SearchMapper#search(java.util.Map,
	 *      java.util.Map)
	 */
	public synchronized List search(Object input, Map env)
			throws MapperException {
		return (List) map(input, env);
	}

	/**
	 * @param param
	 * @return
	 */
	private void instanciateControls(Map param) {
		for (Iterator i = param.entrySet().iterator(); i.hasNext();) {
			Map.Entry e = (Map.Entry) i.next();
			String pn = (String) e.getKey();
			try {
				String vs = (String) e.getValue();
				/* find method in sc class */
				typeHelper.setString(controls, pn, vs);
			} catch (ClassCastException ex) {
				// ignore non strings
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see fr.norsys.mapper.core.jndi.mappers.JNDIMapper#clone()
	 */
	public Object clone() {
		SearchMapperImpl sm = (SearchMapperImpl) super.clone();
		sm.controls = controls;
		return sm;
	}

	/**
	 * Sets the controls to be used in this search.
	 * 
	 * @param controls
	 *            may not be null
	 */
	public void setControls(SearchControls controls) {
		if (controls == null)
			throw new IllegalArgumentException("May not define null controls");
		this.controls = controls;
	}

	/**
	 * Return the current controls for this search.
	 * 
	 * @return the controls used in this search
	 */
	public SearchControls getControls() {
		return controls;
	}

	protected Object doMap(Object input, Map env) throws MapperException {
		/* instanciate fitler */
		String _filter = (String) env.get("filter");
		boolean relative = true;
		if (_filter == null && filter != null) {
			if (input == null)
				throw new MapperException(getName()
						+ ": The input object cannot be null if used as filter");
			input = filter.map(input, env);
			_filter = (String) env.get("filter");
		}
		if (_filter == null) {
			if (log.isInfoEnabled()) {
				log
						.info("["
								+ getName()
								+ "] no filter defined. Will try lookup using only root "
								+ _root);
			}
			relative = false;
		}
		/* instanciate controls */
		instanciateControls(env);
		/* run query */
		try {
			if (log.isDebugEnabled())
				log.debug("[" + getName() + "] searching " + _root + " for "
						+ _filter + " with controls " + controls);
			List ret = new ArrayList();
			if (_filter != null) {
				NamingEnumeration enumeration = null;
				enumeration = directory.search(_root, _filter, controls);
				/* create list of search results */
				while (enumeration.hasMore()) {
					SearchResult res = (SearchResult) enumeration.next();
					ret.add(res);
				}
			} else {
				Attributes attrs = directory.getAttributes(_root, controls
						.getReturningAttributes());
				SearchResult sr = new SearchResult(_root.toString(),null,attrs);
				sr.setRelative(relative);
				ret.add(sr);
			}
			if (log.isDebugEnabled())
				log.debug("[" + getName() + "] done");

			/* output list of objects */
			return output.map(ret, env);
		} catch (NamingException e) {
			throw new MapperException("Exception in JNDI mapping "
					+ e.getLocalizedMessage(), e);
		}
	}

	/**
	 * Defines the type of objects this mapper returns and the transformation
	 * map used.
	 * 
	 * @param cls
	 *            the type of returned objects.
	 * @param map
	 *            the map from LDAP names to Java names.
	 */
	public void setOutput(Class cls, Map map) {
		this.output = new FunctorMapper(OutputMap.create(cls, map));
	}

	/**
	 * Defines the type of filter to use for generating automatic filter from
	 * input object.
	 * 
	 * @param cls
	 * @param map
	 */
	public void setFilter(Class cls, Map map) {
		this.filter = FilterFromInput.create(cls, map);
	}
}
